package Server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.Query;
import org.mortbay.util.ajax.Continuation;
import General.ClientDetails;
import General.Hash;
import Messages.CreateFolderMsg;
import Messages.FileChangeMsg;
import Messages.Msg;
import Messages.MsgType;
import Messages.SendFileMsg;
import ServerDB.Folder;
import ServerDB.FolderManager;
import ServerDB.MapFileServer;
import ServerDB.MapFileServerManager;
import ServerDB.User;
import WizardInstall.ServerDetails;

/**
 * this class is receiving data from all the clients, and open each new client new thread in 
 * thread pool. than it update DB.
 * @author Liel && Guy
 *
 */
public class ServerProtocol implements Runnable{
	ServerSocket providerSocket;
	String serverFilesPath;
	String tempPath;
	Socket connection = null;
	HashMap<String, File> filesHash;
	HashMap<String, Continuation> continuation;
	int currentFileId;
	int tempFileId;
	int port = 8989;
	int maxOfExecutors = 10;
	SendInfo sendToClientsInfo;
	//the thread pool
	private ExecutorService clientRequestPool= Executors.newFixedThreadPool(maxOfExecutors);

	File file=new File("C:\\ourDropBox\\hibernate.cfg.xml");
	SessionFactory sessionFactory=new Configuration().configure(file).buildSessionFactory();
	org.hibernate.classic.Session sessionHibernate=sessionFactory.openSession();

	private static class Holder{
		volatile static ServerProtocol serverProtocolSingelton=new ServerProtocol();
	}
	private ServerProtocol() {
		sendToClientsInfo = SendInfo.getInstance();
		serverFilesPath = new String(ServerDetails.getServerDetailInstance().getServerFilePath()+"\\");
		filesHash = new HashMap<String, File>();
		continuation = new HashMap<String, Continuation>();
		currentFileId = 1;
		tempFileId = 1;
		tempPath = serverFilesPath+ "\\Temp\\";
		File f = new File(tempPath);
		try {
			if (f.mkdir()) {
				System.out.println("Directory Created - in path "+ f.getPath());
			} else
				System.out.println("Directory is not created");
		} catch (Exception e) {
		}
	}
	/**
	 * this is singelton class, so get the instance of the class
	 * @return the class instance
	 */
	public static ServerProtocol getInstance()  {
		return Holder.serverProtocolSingelton;
	}
	/**
	 * add file to hash table
	 * @param checkSum - the file check sum
	 * @param file - the file
	 */
	public void addFile(String checkSum, File file){
		filesHash.put(checkSum, file);
	}
	/**
	 * get the file from hash
	 * @param checkSum - the wanted file's check sum
	 * @return - the wanted file
	 */
	public File getFile(String checkSum){
		return filesHash.get(checkSum);
	}
	/**
	 * delete the file, and remove file from hash table
	 * @param fileName - file name
	 * @param checkSum - file check sum
	 */
	public void removeFile(String fileName, String checkSum){
		filesHash.remove(checkSum);
		File file = new File(fileName);
		file.delete();		
	}
	/**
	 * this is the main method, waiting for connection and save it in thread pool 
	 */
	public void run()
	{
		try{
			//1. creating a server socket
			providerSocket = new ServerSocket(port, 10);
			while (true){
				//2. Wait for connection
				System.out.println("Waiting for connection");
				connection = providerSocket.accept();
				//add it to the thread pool
				clientRequestPool.execute(new RunClientProtocol(connection, sessionHibernate));
			}
		}
		catch(IOException ioException)
		{
			ioException.printStackTrace();
		}
		finally
		{	
			//4: Closing connection
			try
			{
				if (providerSocket!=null)
					providerSocket.close();
			}
			catch(IOException ioException)
			{
				ioException.printStackTrace();
			}
		}
	}

	/**
	 * check if folder exist
	 * @param clientDetails - the client details
	 * @param folderName - folder name
	 * @return - true if folder exst, false o.w
	 */
	public boolean isFolderExist(ClientDetails clientDetails,String folderName)
	{
		String strQueryUser="FROM User e WHERE e.userName=:userNameQry";
		Query query1=sessionHibernate.createQuery(strQueryUser);
		query1.setParameter("userNameQry", clientDetails.getUserName());
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List<User> listUser=query1.list();

		User user = listUser.get(0);
		Folder currentFolder=null;
		System.out.println("folder name is "+folderName + " size of folders is "+user.getFolders().size());
		for (Folder folder : user.getFolders()){
			System.out.println(" - "+ folder.getFolderName());
			if (folder.getFolderName().equals(folderName)){

				currentFolder=folder;
				break;
			}
		}

		if(currentFolder==null)
		{
			return false;
		}
		else
		{
			return true;
		}
	}
	/**
	 * adding the folder record to the data base
	 * @param folderName - the folder name
	 * @param clientDetails - the client that created this folder
	 * @return - the id of the folder
	 */
	public int addFolderToDB(String folderName, ClientDetails clientDetails)
	{
		String strQueryUser="FROM User e WHERE e.userName=:userNameQry";
		Query query1=sessionHibernate.createQuery(strQueryUser);
		query1.setParameter("userNameQry", clientDetails.getUserName());
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List<User> listUsers=query1.list();
		System.out.println("size !!!!!   "+listUsers.size());
		User mainUser=listUsers.get(0);

		Folder folder=new Folder();
		folder.setFolderName(folderName);
		FolderManager folderManager=new FolderManager(sessionHibernate);
		folderManager.saveFolder(folder);
		sessionHibernate.flush();
		mainUser.addFolder(folder);
		folder.addUser(mainUser);

		sessionHibernate.flush();

		return folder.getFolderID();
	}
	/**
	 * share new folder
	 * @param users - the users names that we are going to share with
	 * @param folderName - the folder name
	 * @param clientDetails - the client details who want to share
	 */
	public void shareFolder(ArrayList<String> users,String folderName, ClientDetails clientDetails)
	{
		System.out.println("in share "+folderName);
		String strQueryUser="FROM User e WHERE e.userName=:userNameQry";
		Query query1=sessionHibernate.createQuery(strQueryUser);
		query1.setParameter("userNameQry", clientDetails.getUserName());
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List<User> listUsers=query1.list();
		System.out.println("size !!!!!   !!!!!   "+listUsers.size());
		User mainUser=listUsers.get(0);

		Folder mainFolder = null;
		for (Folder f: mainUser.getFolders()){
			if (f.getFolderName().equals(folderName)){
				mainFolder=f;
				break;
			}
		}
		//go on all the received users
		for(String userName:users)
		{
			strQueryUser="FROM User e WHERE e.userName=:userNameQry";
			query1=sessionHibernate.createQuery(strQueryUser);
			query1.setParameter("userNameQry", userName);
			System.out.println("After SQL line");
			@SuppressWarnings("unchecked")
			List<User> listUser=query1.list();
			if (listUser.isEmpty())
				continue;

			User user = listUser.get(0);
			user.addFolder(mainFolder);
			mainFolder.addUser(user);
			sessionHibernate.flush();

			System.out.println("user "+user.getUserName()+" is going to update");
			Msg msg = new CreateFolderMsg(folderName);
			sendToClientsInfo.addMessage(user.getUserName(), msg);
			System.out.println("put cont of "+user.getUserName());
			Continuation cc = continuation.get(user.getUserName());
			System.out.println("going to resume");
			cc.resume();		
		}
	}
	/**
	 * add file received from server
	 * @param data - the file in byte
	 * @param fileName - the file name
	 * @param folderID - the folder id
	 * @param userName - the user name that sended this request
	 */
	public void addFileFromServer(byte[] data, String fileName, String folderID, String userName){


		System.out.println("new file from server, file name is "+fileName+" from user "+userName+" and folder id is "+folderID);
		String strQueryUser="FROM User e WHERE e.userName=:userNameQry";
		Query query1=sessionHibernate.createQuery(strQueryUser);
		query1.setParameter("userNameQry", userName);
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List<User> listUser=query1.list();
		User user = listUser.get(0);
		Folder currentFolder = null;

		int i=Integer.parseInt(folderID);
		for (Folder f:user.getFolders()){
			if (f.getFolderID()==i){
				currentFolder = f;
				break;
			}
		}
		String folderName = currentFolder.getFolderName();

		System.out.println("folder name now is: "+folderName);

		File tempFile = createTempFile(data, fileName);
		String tempFilePath = tempFile.getPath();
		ClientDetails cd = new ClientDetails(userName, null, null, null, null);
		String checkSum;
		try {
			checkSum = Hash.getHashFile(tempFile.getPath());
			if (!fileExist(cd, fileName, folderName)){
				File newFile = createFile(data, fileName);
				addFile(checkSum, newFile);

			}
			FileChangeMsg message = new FileChangeMsg(MsgType.CREATE, checkSum, fileName, folderName, null);
			addFileToTable(cd, message, tempFile, true);

		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("going to delete file "+tempFile.getPath());
		tempFile = new File(tempFilePath);
		tempFile.delete();
	}
	
	//create temporary file
	private File createTempFile (byte[] fileInByte, String fileName){
		{ 
			File newFile = null;
			synchronized (tempPath){
				newFile = new File(tempPath+tempFileId);
				tempFileId++;
			}
			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(newFile);
				fos.write(fileInByte);
				fos.flush();
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}      
			return newFile;

		}
	}
	/**
	 * create file from bytes[]
	 * @param fileInByte - the file in bytes
	 * @param fileName - the file name
	 * @return the file as file
	 */
	public File createFile (byte[] fileInByte, String fileName){
		{ 
			System.out.println("create in server in name: " + fileName);
			File newFile = null;
			synchronized (serverFilesPath){
				System.out.println("--- " +serverFilesPath);
				newFile = new File(serverFilesPath+currentFileId);
				currentFileId++;
			}
			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(newFile);
				fos.write(fileInByte);
				fos.flush();
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}      
			return newFile;

		}
	}
	/**
	 * add the file to the data base
	 * @param cd - the client details that send the request
	 * @param msg - the message
	 * @param file - the file we want to add
	 * @param includingUser - to we need to add the file to the client that send the request  
	 */
	public void addFileToTable(ClientDetails cd, Msg msg, File file, boolean includingUser){
		String fileName = null;
		String folderName = null;
		String fileCheckSum = null;
		MsgType type = msg.getType();
		System.out.println("%%%%% %%%%% "+type);

		if (type == MsgType.CREATE ||type == MsgType.MODIFY ){
			System.out.println("CREATE");
			FileChangeMsg newMsg = (FileChangeMsg)msg;
			fileName = newMsg.getFileName();
			folderName = newMsg.getFolder();
			fileCheckSum = newMsg.getFileHash();
		}
		else if(type==MsgType.SEND_FILE){
			System.out.println("SEND_FILE");
			SendFileMsg newMsg = (SendFileMsg)msg;
			fileName = newMsg.getFileName();
			folderName = newMsg.getFolderName();
			fileCheckSum = newMsg.getFileCheckSum();

		}

		String strQueryUser="FROM User e WHERE e.userName=:userNameQry";
		Query query1=sessionHibernate.createQuery(strQueryUser);
		query1.setParameter("userNameQry", cd.getUserName());
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List<User> listUser=query1.list();

		User user = listUser.get(0);
		Folder currentFolder=null;
		System.out.println("folder name is "+folderName + " size of folders is "+user.getFolders().size());
		for (Folder folder : user.getFolders()){
			System.out.println(" - "+ folder.getFolderName());
			if (folder.getFolderName().equals(folderName)){

				currentFolder=folder;
				break;
			}
		}
		System.out.println(currentFolder.getFolderID());

		MapFileServer newFile = new MapFileServer();
		newFile.setCheckSum(fileCheckSum);
		System.out.println("file name = "+fileName);
		newFile.setFileName(fileName);
		newFile.setFilePath(file.getPath());
		newFile.setFolderId(currentFolder.getFolderID());

		SendFileMsg newFileMsg = new SendFileMsg(newFile.getFilePath(), newFile.getCheckSum(), 
				currentFolder.getFolderName());
		newFileMsg.setFileName(newFile.getFileName());


		System.out.println("going to update other users in add at msg "+newFileMsg.getType());
		updateOtherUsers(newFileMsg, currentFolder, cd, includingUser);

		MapFileServerManager manager = new MapFileServerManager(sessionHibernate);
		manager.saveMFS(newFile);

		sessionHibernate.flush();
	}

	/** 
	 * check if file exist in server
	 * @param cd - client details
	 * @param fileName - file's name
	 * @param folderName - folder's name
	 * @return true if file exist, false other wise
	 */
	public boolean fileExist(ClientDetails cd, String fileName, String folderName){
		System.out.println(fileName + "    &&&    " + folderName);
		String strQuery="FROM User WHERE USER_NAME =:userNameQry";
		Query query=sessionHibernate.createQuery(strQuery);
		query.setParameter("userNameQry", cd.getUserName());
		@SuppressWarnings("unchecked")
		List<User> listUsers=query.list();
		User user = listUsers.get(0);
		Set<Folder> userFolders = user.getFolders();
		int folderId = -1;
		for (Folder f: userFolders){
			if (f.getFolderName().equals(folderName)){
				folderId = f.getFolderID();
				break;
			}
		}
		strQuery="FROM MapFileServer WHERE FILE_NAME =:fileNameQry AND FOLDER_ID =:folderIdQry";
		query=sessionHibernate.createQuery(strQuery);
		query.setParameter("fileNameQry", fileName);
		query.setParameter("folderIdQry", folderId);
		@SuppressWarnings("unchecked")
		List<MapFileServer> listFiles=query.list();
		if (listFiles.size()==0){
			System.out.println("size is 0");
			return false;
		}
		else{
			System.out.println("size is "+listFiles.size());
			return true;
		}
	}


	/**
	 * delete the file
	 * @param clientDetails
	 * @param msg - the message received
	 */
	public void DeleteFile(ClientDetails clientDetails,FileChangeMsg msg){

		String fileName = msg.getFileName();
		String folderName = msg.getFolder();
		String userName = clientDetails.getUserName();
		System.out.println("going to del file "+fileName + " " + folderName);

		String strQuery="FROM User WHERE USER_NAME =:userNameQry";
		Query query=sessionHibernate.createQuery(strQuery);
		query.setParameter("userNameQry", userName);
		System.out.println("After SQL 1 line ->"+userName);
		@SuppressWarnings("unchecked")
		List<User> listUsers=query.list();
		//		itUser.hasNext()
		if (listUsers.size()==0){
			System.out.println("000000000000000000");
			System.out.println(userName);
			System.out.println("000000000000000000");
			return;
		}

		User user = listUsers.get(0);
		Set<Folder> listOfFolders = user.getFolders();
		System.out.println(user.getUserName() + "   " + listOfFolders.size());
		Folder currentFolder=null;
		for (Folder folder: listOfFolders){
			if (folder.getFolderName().equals(folderName))
				currentFolder = folder;
		}
		if (currentFolder==null){
			System.out.println("wrong folder name!!!!!!!");
			return;
		}
		else
			System.out.println("folder name is "+currentFolder.getFolderName());
		int folderId = currentFolder.getFolderID();

		//get file check sum

		strQuery="FROM MapFileServer WHERE FOLDER_ID =:folderIdQry AND FILE_NAME =:fileNameQry";
		query=sessionHibernate.createQuery(strQuery);
		System.out.println("data = " + folderId + " " + fileName);
		query.setParameter("folderIdQry", folderId);
		query.setParameter("fileNameQry", fileName);
		System.out.println("After SQL 2 line");
		@SuppressWarnings("unchecked")
		List<MapFileServer> files =query.list();
		//if the file was already deleted
		if (files.size()==0)
			return;
		MapFileServer file = files.get(0);
		String checkSum = file.getCheckSum();

		//delete record from DB
		System.out.println("del file "+ file.getFileID());
		sessionHibernate.delete(file);
		sessionHibernate.flush();
		strQuery="FROM MapFileServer WHERE CHECK_SUM =:checkSumQry";
		query=sessionHibernate.createQuery(strQuery);
		query.setParameter("checkSumQry", checkSum);
		@SuppressWarnings("unchecked")
		List<MapFileServer> remainFiles = query.list();

		System.out.println("update users in del ");
		if (msg.getType()!=MsgType.MODIFY)
			updateOtherUsers(msg, currentFolder, clientDetails, false);


		if (remainFiles.size()!=0)
			return;
		System.out.println("left files "+remainFiles.size());
		//else
		String nameInServer = file.getFilePath();
		removeFile(nameInServer, checkSum);



		return;
	}
	//update all other users
	private void updateOtherUsers(Msg msg, Folder folder, ClientDetails clientDetails, boolean includingUser){
		Set<User> users =  folder.getUsers();
		for (User user : users){
			if (!includingUser && user.getUserName().equals(clientDetails.getUserName()))
				continue;
			else{
				System.out.println("user "+user.getUserName()+" is going to update");
				sendToClientsInfo.addMessage(user.getUserName(), msg);
				System.out.println("put cont of "+user.getUserName());
				Continuation cc = continuation.get(user.getUserName());
				if (cc!=null){
					System.out.println("going to resume");
					cc.resume();
				}
			}
		}//end of for
	}
	/**
	 * check if the user is valid user name and password
	 * @param details - receive the client details
	 * @return true of user and password are in the DB, false o.w
	 */
	public boolean IsUserValid (ClientDetails details){

		System.out.println("trying to validate user name and password");

		String strQueryUser1="FROM User";
		Query query1=sessionHibernate.createQuery(strQueryUser1);
		System.out.println("After SQL line");
		@SuppressWarnings("unchecked")
		List <User> listOfUsers=query1.list();


		String userName = details.getUserName();
		String password = details.getPassword();

		System.out.println("entered userName = "+userName+" and pass = "+password);

		for (User user : listOfUsers){
			System.out.println("user's userName = "+user.getUserName()+" and pass = "+user.getPassword());
			if (userName.equals(user.getUserName()) && password.equals(user.getPassword())){
				System.out.println("ret true");
				return true;
			}
		}
		System.out.println("ret false");
		return false;
	}
	/**
	 * add continuation to hash
	 * @param cc - the continuation object
	 * @param userName - the user name
	 */
	public void addContinuation(Continuation cc, String userName){
		continuation.put(userName,cc);
		//check if there are messages to client
		if (sendToClientsInfo.isEmpty(userName)){
			//if not, suspend the client
			System.out.println("suspending");
			cc.suspend(0);
			System.out.println("after suspend");
		}
	}
}
